package sfbot;

import java.util.AbstractQueue;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.SortedSet;
import java.util.Stack;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentLinkedQueue;

public class LogButler implements CommandModel {
	private final String command = "log";
	private Map<Channel, Map<User, Queue<LogLine>>> logUserMap;
	private Map<Channel, Queue<LogLine>> logChannelMap;
	
	private LogDatabase database;
	
	private boolean verbose = true;
	
	public LogButler(){
		logUserMap = new TreeMap<Channel, Map<User, Queue<LogLine>>>();
		logChannelMap = new TreeMap<Channel, Queue<LogLine>>();
		database = new TextFile("loadLog.in", "saveLog.out", new ArrayList<User>());
	}
	
	@Override
	public void activate() {
		// TODO activate method

	}

	@Override
	public void deactivate() {
		// TODO deactivate method

	}

	@Override
	public List<String> getCommands() {
		LinkedList<String> cmd = new LinkedList<String>();
		cmd.add(command);
		return cmd;
	}

	@Override
	public void recieveMessage(String msg, User user, Channel channel) {
		if(msg.indexOf(command) != 0)
			recordMessage(msg, user, channel);
		else {
			List<User> persons = new ArrayList<User>();
			persons.add(user);
			try {
				List<LogLine> userMsgs = grabUserMsgs(channel, user, persons);
				//logLines(3, userMsgs, user, channel);
				Calendar c = Calendar.getInstance();
				c.roll(Calendar.SECOND, -6);
				logTime(c, userMsgs, user, channel);
			} catch (InvalidLogException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}

	@Override
	public boolean status() {
		// TODO write status method
		return false;
	}
	
	/**
	 * Records all messages in the channel to a User Map & the Channel Map.
	 * @param msg the message the user sent.
	 * @param user the user who sent the message.
	 * @param channel the channel the user is in.
	 */
	private void recordMessage(String msg, User user, Channel channel){
		LogLine line = new LogLine(msg, user, channel);
		
		// Save into Channel Map
		if(logChannelMap.containsKey(channel)){
			if(!logChannelMap.get(channel).offer(line))
					verbose(String.format("Failed to add %s to logChannelMap.", user.getNick()));
		} else {
			// Create a channel message list
			Queue<LogLine> channelMsgs = new ConcurrentLinkedQueue<LogLine>();
			channelMsgs.offer(line);
			logChannelMap.put(channel, channelMsgs);
			verbose("Created a new key '" + channel + "' for LOGCHANNELMAP.");
		}
		
		// Save into User Map
		if(logUserMap.containsKey(channel)){
			Map<User, Queue<LogLine>> userChannel = logUserMap.get(channel);
			if(userChannel.containsKey(user)){
				userChannel.get(user).offer(line);
			} else {
				// Create a user message list
				Queue<LogLine> userMsg = new ConcurrentLinkedQueue<LogLine>();
				userMsg.offer(line);
				userChannel.put(user, userMsg);
				verbose("Created a new <User> key '" + user + "' for LOGUSERMAP.");
			}
		} else {
			// Create a user map and user message list
			Map<User, Queue<LogLine>> userChannel = Collections.synchronizedMap(
					new HashMap<User, Queue<LogLine>>()
			);
			synchronized(userChannel){
				Queue<LogLine> userMsg = new ConcurrentLinkedQueue<LogLine>();
				userMsg.offer(line);
				userChannel.put(user, userMsg);
				logUserMap.put(channel, userChannel);
				verbose("Created a new <Channel> key for '" + channel + "' for LOGUSERMAP & " +
						"\n\tCreated a new <User> key '" + user + "' for LOGUSERMAP");
			}
		}
		
		
	}
	
	
	/**
	 * Constructs a list of LogLines from the list of users provided. This list will be sorted
	 * and contain as many lines as memory will allow. 
	 * @param channel the channel you wish to record from
	 * @param source the user who made the request
	 * @param users the list of users who the source wishes to record
	 * @return the entire log of conversations (in order) from the list of users
	 * @throws InvalidLogException if the channel is not found
	 */
	private List<LogLine> grabUserMsgs(Channel channel, User source, List<User> users) throws InvalidLogException{
		// Check for no-users
		if(users == null || users.isEmpty())
			throw new InvalidLogException("Error with logging source.");
		
		// Grab all the lines of code from the users and combine them into a single
		// sorted list.
		List<LogLine> logList = new ArrayList<LogLine>();
		List<User> notFound = new ArrayList<User>();
		if(logUserMap.containsKey(channel)){
			Map<User, Queue<LogLine>> userMap = logUserMap.get(channel);
			for(User u : users){
				if(userMap.containsKey(u)){
					logList.addAll(userMap.get(u));
				} else {
					notFound.add(u);
				}
			}
			for(LogLine l : logList)
				verbose("Pre: " + l);
			// Sort the list of logs
			Collections.sort(logList, new Comparator<LogLine>(){
				@Override
				public int compare(LogLine a, LogLine b) {
					return -(a.compareTo(b));
				}				
			});
			if(!notFound.isEmpty()){
				String errorMsg = "";
				for(User u : notFound){
					verbose(String.format("WARNING: Unable to find: %s", u.getNick()));
					sendMessage(source.getNick(), String.format("WARNING: Unable to find: %s", u.getNick()));
				}
			}
		} else {
			throw new InvalidLogException("Channel '" + channel + "' does not exist.");
		}
		return logList;
	}
	
	/**
	 * Backlogs and records messages according to a numeric number of messages.
	 * @param numOfLines the number of (total) lines that will be backlogged for recording
	 * @param logList the list of LogLines that will be iterated. (should be sorted)
	 * @param source the user who made the request
	 * @param channel the channel the recording was made from
	 * @throws InvalidLogException an invalid number of lines was requested
	 * @throws UserDoesNotExistException
	 */
	private void logLines(int numOfLines, List<LogLine> logList, User source, Channel channel) throws InvalidLogException, UserDoesNotExistException{
		// Check for negative lines of code
		if(numOfLines <= 0)
			throw new InvalidLogException("Cannot log '" + numOfLines + "' lines");
		
		// Now only add the nth newest lines of code
		Stack<LogLine> msgStack = new Stack<LogLine>();
		int max = 0;
		for(LogLine l : logList){
			if(++max > numOfLines)
				break;
			msgStack.push(l);
			verbose("Push: " + l.toString());
		}		
		writeOut(msgStack);	
		
	}
	
	/**
	 * @param cutoff all messages older than this time will be recorded.
	 * @param logList the list of messages that will be recorded
	 * @param src the user who made the request
	 * @param channel the channel the recording was made from
	 * @throws InvalidLogException an invalid time was requested
	 */
	private void logTime(Calendar cutoff, List<LogLine> logList, User src, Channel channel) throws InvalidLogException {
		// Check for negative time
		if(cutoff == null)
			throw new InvalidLogException("Cannot see the future '-" + cutoff.toString() + "' seconds");
		
		// Now only add the nth newest lines of code
		Stack<LogLine> msgStack = new Stack<LogLine>();
		int max = 0;
		for(LogLine l : logList){
			if(cutoff.after(l.getTimeStamp()))
				break;
			msgStack.push(l);
			verbose("Push: " + l.toString());
		}		
		writeOut(msgStack);	
	}
	
	
	private void writeOut(Stack<LogLine> msgStack) {
		List<String> tags = new ArrayList<String>();
		tags.add("irc");
		tags.add("test");
		database.write(msgStack, tags);
		while(!msgStack.isEmpty()){
			System.out.println(msgStack.pop());
		}
	}
	
	private void sendMessage(String dest, String msg){
		System.out.println(String.format("[IRC:%-10s] %s", dest, msg));
	}

	private void logChannelLine(int numOfLines, Channel channel,
			List<User> users, Map<User, AbstractQueue<LogLine>> userMap) throws UserDoesNotExistException{
		SortedSet<LogLine> sorter = new TreeSet<LogLine>(
				new Comparator<LogLine>(){
					@Override
					public int compare(LogLine a, LogLine b) {
						return (a.compareTo(b));
					}					
				}
		);
		
		for(User u : users){
			if(userMap.containsKey(u)){
				try {
					sorter.addAll(userMap.get(u));
				} catch (NullPointerException e){
					// TODO add pretty error message for null pointers
				}
			} else {
				throw new UserDoesNotExistException("User '" + u.getNick() + "' does not exsits.");
			}
			
		}
		
		
	}

	private void verbose(String msg) {
		if(verbose )
			System.out.println(msg);
	}

	

}
